﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *  
 **/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
using Peter.Common;
using Peter.Common.Interfaces;
using Peter.Common.MainMenu;
using Peter.Common.Services;
using Peter.Data;
using Peter.Properties;
using Peter.Logic.Interfaces;

namespace Peter.Logic
{
   /// <summary>
   /// Represents the options dialog.
   /// </summary>
   [Export (typeof (IPeterOptions))]
   [PartCreationPolicy (CreationPolicy.Shared)]
   public sealed class ModelOptions : PeterDocument, IPeterOptions, IPartImportsSatisfiedNotification
   {
      private const string DATA_FILE = "Peter.dat";
      private object m_SelectedItem;
      private bool m_IsSaving;

      /// <summary>
      /// Initializes a new options model.
      /// </summary>
      public ModelOptions ()
      {
         this.Title = "Options";
         this.IsComposed = true;
         this.Icon = MainMenuIcon.Cog;
         this.ContentId = Guids.OPTIONS;
         this.ToolTip = "Edit Options for Peter";
         this.OptionPages = new ObservableCollection <IOptionPage> ();
      }

      /// <summary>
      /// Gets the list of option pages.
      /// </summary>
      [ImportMany (typeof (IOptionPage))]
      public IEnumerable<IOptionPage> OptionPageList { get; set; }

      /// <summary>
      /// Gets the list of additional options.
      /// </summary>
      [ImportMany (typeof (IOptionData))]
      public IEnumerable<IOptionData> AdditionalOptions { get; set; }

      /// <summary>
      /// Gets the editor options.
      /// </summary>
      [XmlElement]
      public OptionsEditor EditorOptions { get; private set; }

      /// <summary>
      /// Gets the items that have options.
      /// </summary>
      [XmlIgnore]
      public ObservableCollection <IOptionPage> OptionPages { get; private set; }

      /// <summary>
      /// Gets or Sets the selected option item.
      /// </summary>
      [XmlIgnore]
      public object SelectedItem
      {
         get { return this.m_SelectedItem; }
         set
         {
            this.m_SelectedItem = value;
            this.OnPropertyChanged ("SelectedItem");
         }
      }

      /// <summary>
      /// Gets the options page with the given name.
      /// </summary>
      /// <param name="pageName">Name of option page to get.</param>
      /// <returns>The requested option page, null if non-existant.</returns>
      public IOptionPage GetOptionPage (string pageName)
      {
         return this.OptionPages.FirstOrDefault (oi => oi.Name == pageName);
      }

      /// <summary>
      /// Called when a part's imports have been satisfied and it is safe to use.
      /// </summary>
      public void OnImportsSatisfied ()
      {
         // Sort the option pages...
         this.OptionPages = new ObservableCollection <IOptionPage> {
            this.OptionPageList.First (op => op.Name == "General"),
            this.OptionPageList.First (op => op.Name == "Editor")
         };
         foreach (var optionPage in this.OptionPageList.Where (op => op.Name != "General" && op.Name != "Editor"))
            this.OptionPages.Add (optionPage);

         this.EditorOptions = (OptionsEditor) this.GetOptionPage ("Editor");
         this.SelectedItem = this.OptionPages[0];
         this.Peter.ShuttingDown += this.OnShutdown;
      }

      /// <summary>
      /// Save the current options.
      /// </summary>
      public void Save ()
      {
          var methodInvoker = ServiceManager.Get<IMethodInvoker>();
          methodInvoker.InvokeMethod<ICollection<IOptionPage>, ICollection<IOptionData>> (SaveThread,
                                                                                          this.OptionPageList.ToList(),
                                                                                          this.AdditionalOptions.ToList (),
                                                                                          null);
      }

      /// <summary>
      /// Gets the option data with the given name.
      /// </summary>
      /// <param name="dataName">Name of option data.</param>
      /// <returns>The requested option data.</returns>
      public IOptionData GetOptionData (string dataName)
      {
         return this.AdditionalOptions.FirstOrDefault (
            ad => ad.Name.Equals (dataName, StringComparison.CurrentCultureIgnoreCase));
      }

      /// <summary>
      /// Threaded method to save options.
      /// </summary>
      /// <param name="optionPages">List of option pages to save.</param>
      /// <param name="additionalOptions">Any additional options to be saved</param>
      private void SaveThread (ICollection<IOptionPage> optionPages, ICollection<IOptionData> additionalOptions)
      {
         var timeOut = 0;
         while (this.m_IsSaving)
         {
            timeOut++;
            Thread.Sleep (500);
            if (timeOut >= 5)
            {
               var messageBox = ServiceManager.Get <IMessageBoxService> ();
               messageBox.ShowError (Resources.BadSaveOptions);
               return;
            }
         }

         this.m_IsSaving = true;
         using (var fileWriter = new StreamWriter (Path.Combine(CommonUtilities.GetAssemblyDirectory (), DATA_FILE), false, Encoding.UTF8))
         {
            var xmlWriter = new XmlTextWriter (fileWriter) { Formatting = Formatting.Indented, Indentation = 3 };
            xmlWriter.WriteStartDocument ();
            xmlWriter.WriteStartElement ("PeterOptions");
            foreach (var optionItem in optionPages)
            {
               var optionXml = optionItem.SaveToXml ();
               WriteOptionData (xmlWriter, optionXml, optionItem);
            }

            foreach (var optionItem in additionalOptions)
            {
               var optionXml = optionItem.SaveToXml ();
               WriteOptionData (xmlWriter, optionXml, optionItem);
            }

            xmlWriter.WriteEndElement ();
            xmlWriter.WriteEndDocument ();
         }
         this.m_IsSaving = false;
      }

      private static void WriteOptionData (XmlTextWriter xmlWriter, string optionXml, IOption optionItem)
      {
         if (!string.IsNullOrEmpty (optionXml))
         {
            var type = optionItem.GetType ();
            var pageName = optionItem.Name.Replace (' ', '_').Replace ('.', '_');
            var typeName = type.FullName ?? pageName;
            typeName = typeName.Replace ('.', '_');
            xmlWriter.WriteStartElement (typeName);
            xmlWriter.WriteRaw (optionXml);
            xmlWriter.WriteEndElement ();
         }
      }

      /// <summary>
      /// Closes the current item.
      /// </summary>
      /// <param name="obj">Command parameter.</param>
      protected override void OnClose (object obj)
      {
         this.Save ();
         base.OnClose (obj);
      }

      /// <summary>
      /// Occurs when peter is shutting down.
      /// </summary>
      /// <param name="sender">IPeter.</param>
      /// <param name="e">Event Args</param>
      private void OnShutdown (object sender, EventArgs e)
      {
         this.Peter.ShuttingDown -= this.OnShutdown;
         this.SaveThread (this.OptionPageList.ToList (), this.AdditionalOptions.ToList ());
      }
   }
}
